package tools.csv;

import java.io.BufferedReader;
import java.io.FileReader;
import java.io.InputStreamReader;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedList;

import tools.neoBio.neoBioUtils;
import tools.utils.general;

public class csvUtils {
	
	final static String sep= "\t";

	public static void main(String[] args) throws Exception{
		if(args.length>0){
			if(args[0].equals("topAsc")&&args.length==5){
				top(args[1],Integer.parseInt(args[2]),Integer.parseInt(args[3]),Integer.parseInt(args[4]),true);
			}else if(args[0].equals("topDesc")&&args.length==5){
				top(args[1],Integer.parseInt(args[2]),Integer.parseInt(args[3]),Integer.parseInt(args[4]),false);
			}else if(args[0].equals("join")&&args.length>3){
				ArrayList<String> files= new ArrayList<String>();
				for(int i=1;i<args.length-1;i++){
					files.add(args[i]);
				}
				join(files,Integer.parseInt(args[args.length-1]));
			}else if(args[0].equals("leftjoin")&&args.length>3){
				ArrayList<String> files= new ArrayList<String>();
				for(int i=1;i<args.length-1;i++){
					files.add(args[i]);
				}
				leftjoin(files,Integer.parseInt(args[args.length-1]));
			}else if(args[0].equals("nLargest")&&args.length==4){
				nLargestNumber(args[1],Integer.parseInt(args[2]),Integer.parseInt(args[3]));
			}else if(args[0].equals("tabulatedTopOnly")&&args.length==2){
				//other method
			}else if(args[0].equals("counts")&&args.length>2){
				counts(args[1],Integer.parseInt(args[2]));
			}else if(args[0].equals("slidingWindowHighLow")&&args.length>5){
				slidingWindowHighLow(general.getBufferedReader(args[1]),Double.parseDouble(args[2]),Integer.parseInt(args[3]),Integer.parseInt(args[4]),Integer.parseInt(args[5]));
			}else{
				System.err.println(printHelp());
				System.exit(616);
			}
		}else{
			System.err.println(printHelp());
			System.exit(616);
		}
	}
	private static String printHelp(){
		String help="Usage: csvUtils <cmd> <input>\n";
		help+="where <cmd> is:\n";
		help+="top(Asc/Desc) - extracts the top n results for column p with regard to column s in a csv file (tab separated). Column s must hold numbers. Column count starts with 0.\n";
		help+="\t<input> = <csvFile> <p> <s> <n>\n";
		help+="join - joins the the csv-files on the key column given by the number (starting from 0)\n";
		help+="\t<input> = <csvFile 1> .. <csvFile n> <key>\n";
		help+="leftjoin - joins the the second to nth csv-files on the key column given by the number (starting from 0) to the first one\n";
		help+="\t<input> = <csvFile 1> .. <csvFile n> <key>\n";
		help+="counts - creates a count table for column n (column count starts with 0)\n";
		help+="\t<input> = <csvFile> <n>\n";
		help+="nLargest - Select N largest from column i\n";
		help+="\t<input> = <csvFile (- stdin)> <i> <N>\n";
		help+="slidingWindowHighLow - assumes a bisSNP bed format file. Counts the number above and below the frequency cutoff that passes the coverage cutoff.\n";
		help+="\t<input> = <bisSNP bed file> <frequency cutoff> <coverage cutoff> <sliding window size> <step size>\n";
		
		
		return help;
	}
	
	public static void slidingWindowHighLow(BufferedReader bedFile,final double freqCut,final int covCut, final int winSize,final int stepSize)throws Exception{
		bedFile.readLine(); // remove header
		String 						curChr="";
		int							countHigh=0,
									countLow=0,
									rowCount=0;
		LinkedList<String> 			positions= new LinkedList<String>();
		LinkedList<Integer>			freqAssignment= new LinkedList<Integer>();
		
		
		for(String s= bedFile.readLine();s!=null;s= bedFile.readLine(),++rowCount){
			if(rowCount%1000==0){
				System.err.print(rowCount+"\r");
			}
			String[] l= s.split("\t");
			if(l.length>5){
				if(!curChr.equals(l[0])){
					//reset
					countHigh=0;
					countLow=0;
					positions= new LinkedList<String>();
					freqAssignment= new LinkedList<Integer>();
					curChr=l[0];
				}
				positions.add(l[1]);
				if(Integer.parseInt(l[4])<covCut){
					freqAssignment.add(0);
				}else{
					if(Double.parseDouble(l[3])<freqCut){
						++countLow;
						freqAssignment.add(-1);
					}else{
						++countHigh;
						freqAssignment.add(1);
					}
				}
				if(freqAssignment.size()>winSize){
					while(freqAssignment.size()>winSize){
						switch (freqAssignment.pop()) {
						case -1:
							--countLow;
							break;
						case 1:
							--countHigh;
							break;
						default:
							break;
						}
						positions.pop();
					}
					System.out.println(curChr+sep+positions.peekFirst()+sep+l[2]+sep+countHigh);
					System.out.println(curChr+sep+positions.peekFirst()+sep+l[2]+sep+(-countLow));
					while(freqAssignment.size()>winSize-stepSize){
						switch (freqAssignment.pop()) {
						case -1:
							--countLow;
							break;
						case 1:
							--countHigh;
							break;
						default:
							break;
						}
						positions.pop();
					}
				}
			}
		}
		System.err.print(rowCount+"\n");
	}
	
	public static void nLargestNumber(String file,int i, int N)throws Exception{
		BufferedReader in;
		if(file.equals("-")){
			in= new BufferedReader(new InputStreamReader(System.in));
		}else{
			in= new BufferedReader(new FileReader(file));
		}
		String[] l;
		double d;
		LinkedList<Double> data= new LinkedList<Double>();
		boolean unSorted=true;
		int count=0;
		for(String s= in.readLine();s!=null;s=in.readLine(),++count){
			if(count%10000==0){
				if(unSorted){
					System.err.print("     "+count+"\r");
				}else{
					System.err.print("     "+count+"  smallest:"+data.get(0)+"\r");
				}
			}
			
			l=s.split("\t");
			if(l.length>i){
				d=Double.parseDouble(l[i]);
				if(unSorted || d>=data.getFirst()){
					data.add(d);
				}
			}
			if(data.size()>1.5*N){
				Collections.sort(data);
				while(data.size()>N){
					data.poll();
				}
				unSorted=false;
			}
		}
		Collections.sort(data);
		System.err.println("     "+count+"  smallest:"+data.get(0));
		while(data.size()>N){
			data.poll();
		}
		while(data.size()>0){
			System.out.println(data.poll());
		}
	}
	
	public static void counts(String file,int n)throws Exception{
		BufferedReader in=new BufferedReader(new FileReader(file));
		HashMap<String, Integer> counts=new HashMap<String, Integer>();
		String[] l;
		for(String s=in.readLine();s!=null;s=in.readLine()){
			l=s.split("\t");
			if(n>=l.length){
				System.err.println("line does not contain n="+n+"columns:\n"+s);
			}else{
				if(counts.containsKey(l[n])){
					counts.put(l[n], counts.get(l[n])+1);
				}else{
					counts.put(l[n], 1);
				}
			}
		}
		//print
		for (String value : counts.keySet()) {
			System.out.println(value+sep+counts.get(value));
		}
		in.close();
	}
	
	public static void join(ArrayList<String>files, int key)throws Exception{
		ArrayList<Integer> keys= new ArrayList<Integer>();
		for(int i=0;i<files.size();i++){
			keys.add(key);
		}
		join(files,keys);
	}
	
	public static void leftjoin(ArrayList<String> files, int key) throws Exception{
		ArrayList<Integer> keys= new ArrayList<Integer>();
		for(int i=0;i<files.size();i++){
			keys.add(key);
		}
		leftjoin(files, keys);
	}
	
	public static HashMap<String, ArrayList<String[]>> join_gatherKeys(ArrayList<Integer> rowLengths,HashSet<String> keyNames,String fileName,int curKey)throws Exception{
		HashMap<String, ArrayList<String[]>> curMap= new HashMap<String, ArrayList<String[]>>();
		BufferedReader file= new BufferedReader(new FileReader(fileName));
		String s=file.readLine();
		rowLengths.add(s.split(sep).length);
		if(rowLengths.get(rowLengths.size()-1)-1<curKey){
			file.close();
			throw new Exception("ERROR: The key index ("+curKey+") in file "+fileName+" is higher than the number of items in a row ("+rowLengths.get(rowLengths.size()-1));
		}
		String[] l=new String[1];
		for(;s!=null;s=file.readLine()){
			try{
				l=s.split(sep);
				keyNames.add(l[curKey]);
				if(!curMap.containsKey(l[curKey])){
					
					curMap.put(l[curKey], new ArrayList<String[]>());
				}
				curMap.get(l[curKey]).add(l);
			}catch (NullPointerException e) {
				// TODO: handle exception
				System.out.println("ERR:"+s);
				System.out.println(l.length);
			}
		}
		file.close();
		return curMap;
	}
	
	public static HashMap<String, String[]> join_gatherUniqueKeys(ArrayList<Integer> rowLengths,HashSet<String> keyNames,String fileName,int curKey)throws Exception{
		HashMap<String, String[]> curMap= new HashMap<String, String[]>();
		for (ArrayList<String[]> v : join_gatherKeys(rowLengths, keyNames, fileName, curKey).values()) {
			if(v.size()>1){
				throw new Exception("ERROR The key for file "+fileName+" is not unique");
			}else{
				curMap.put(v.get(0)[curKey], v.get(0));
			}
		}
		return curMap;
	}
	
	public static HashMap<String, ArrayList<String[]>> join_gatherAlreadyPresentKeys(ArrayList<Integer> rowLengths,HashSet<String> keyNames,String fileName,int curKey)throws Exception{
		HashMap<String, ArrayList<String[]>> curMap= new HashMap<String, ArrayList<String[]>>();
		BufferedReader file= new BufferedReader(new FileReader(fileName));
		String s=file.readLine();
		rowLengths.add(s.split(sep).length);
		if(rowLengths.get(rowLengths.size()-1)-1<curKey){
			file.close();
			throw new Exception("ERROR: The key index ("+curKey+") in file "+fileName+" is higher than the number of items in a row ("+rowLengths.get(rowLengths.size()-1));
		}
		String[] l;
		for(;s!=null;s=file.readLine()){
			l=s.split(sep);
			if(keyNames.contains(l[curKey])){
				if(!curMap.containsKey(l[curKey])){
					curMap.put(l[curKey], new ArrayList<String[]>());
				}
				curMap.get(l[curKey]).add(l);
			}
		}
		file.close();
		return curMap;
	}
	
	private static void join_printMap(ArrayList<HashMap<String, ArrayList<String[]>>> map, ArrayList<Integer> rowLengths,HashSet<String> keyNames,ArrayList<String> files,ArrayList<Integer> keys)throws Exception{
		//print
		 //header
		System.out.print("key");
		for(int i=0;i<files.size();i++){
			final String file=files.get(i);
			for(int j=0;j<rowLengths.get(i)-1;j++){
				System.out.print(sep+file);
			}
		}
		System.out.println();
		 //bulk
		for (String key : keyNames) {
			join_printMap_line_recursive(map, rowLengths, keys, key, 0, key);
		}
	}
	
	private static void join_printMap_line_recursive(ArrayList<HashMap<String, ArrayList<String[]>>> map,ArrayList<Integer> rowLengths,ArrayList<Integer> keys, String key, int level,String curLine){
		
		if(level==map.size()){
			//print
			System.out.println(curLine);
		}else{
			//recurse
			String addon;
			int curKey=keys.get(level);
			if(map.get(level).containsKey(key)){
				for (String[] l : map.get(level).get(key)) {
					addon="";
					for(int i=0;i<rowLengths.get(level);i++){
						if(i!=curKey){
							addon+=sep+l[i];
						}
					}
					join_printMap_line_recursive(map, rowLengths, keys, key, level+1, curLine+addon);
				}
			}else{
				addon="";
				for(int i=0;i<rowLengths.get(level)-1;i++){
					addon+=sep+"N/A";
				}
				join_printMap_line_recursive(map, rowLengths, keys, key, level+1, curLine+addon);
			}
		}
	}
	
	public static void leftjoin(ArrayList<String> files,ArrayList<Integer> keys) throws Exception{
		if(files.size()!=keys.size()){
			throw new Exception("ERROR: Different amount of files and keys");
		}
		ArrayList<Integer> rowLengths= new ArrayList<Integer>(); //As estimated from the first row
		//ArrayList <HashMap<key,ArrayList<rows>>>
		ArrayList<HashMap<String, ArrayList<String[]>>> map= new ArrayList<HashMap<String,ArrayList<String[]>>>();
		
		HashSet<String> keyNames= new HashSet<String>();
		
//		String[] l;
//		int curKey;
		map.add(join_gatherKeys(rowLengths, keyNames, files.get(0), keys.get(0)));
		for(int i=1;i<files.size();i++){
			map.add(join_gatherAlreadyPresentKeys(rowLengths, keyNames, files.get(i), keys.get(i)));
		}
		//print
		join_printMap(map, rowLengths, keyNames, files, keys);
	}
	
	public static void join(ArrayList<String>files,ArrayList<Integer> keys) throws Exception{
		if(files.size()!=keys.size()){
			throw new Exception("ERROR: Different amount of files and keys");
		}
		ArrayList<Integer> rowLengths= new ArrayList<Integer>(); //As estimated from the first row
		//HashMap <file, <key, row>>
		ArrayList< HashMap<String, String[]>> map= new ArrayList<HashMap<String,String[]>>();
		//HashMap <key,row>
//		HashMap<String, String[]> curMap;
		HashSet<String> keyNames=new HashSet<String>();
//		BufferedReader in;
//		String s;
		String[] l;
		int curKey;
		//generate data structure
		for(int i=0;i<files.size();i++){
//			in= new BufferedReader(new FileReader(files.get(i)));
//			s=in.readLine();
//			rowLengths.add(s.split(sep).length);
//			if(rowLengths.get(i)-1<keys.get(i)){
//				throw new Exception("ERROR: The key index ("+keys.get(i)+") in file "+files.get(i)+" is higher than the number of items in a row ("+rowLengths.get(i));
//			}
//			curKey=keys.get(i);
//			curMap= new HashMap<String, String[]>();
//			for(;s!=null;s=in.readLine()){
//				l=s.split(sep);
//				if(curMap.containsKey(l[curKey])){
//					throw new Exception("ERROR The key for file "+files.get(i)+" is not unique");
//				}
//				curMap.put(l[curKey], l);
//				keyNames.add(l[curKey]);
//			}
			map.add(join_gatherUniqueKeys(rowLengths, keyNames, files.get(i), keys.get(i)));
		}
		//print
		 //header
		System.out.print("key");
		for(int i=0;i<files.size();i++){
			final String file=files.get(i);
			for(int j=0;j<rowLengths.get(i)-1;j++){
				System.out.print(sep+file);
			}
		}
		System.out.println();
		 //bulk
		for (String key : keyNames) {
			System.out.print(key);
			for(int i=0;i<files.size();i++){
				if(map.get(i).containsKey(key)){
					l=map.get(i).get(key);
					curKey=keys.get(i);
					for(int j=0;j<rowLengths.get(i);j++){
						if(j!=curKey){
							System.out.print(sep+l[j]);
						}
					}
				}else{
					for(int j=0;j<rowLengths.get(i)-1;j++){
						System.out.print(sep+"N/A");
					}
				}
			}
			System.out.println();
		}
		
	}
	
	public static void top(String csvFile, int parentField, int sortField, int number,boolean asc)throws Exception{
		BufferedReader in = new BufferedReader(new FileReader(csvFile));
		String[] l;
		csvUtils_sortObject so;
		ArrayList<csvUtils_sortObject> sortList;
		HashMap<String, ArrayList<csvUtils_sortObject>> tops= new HashMap<String, ArrayList<csvUtils_sortObject>>();
		HashMap<String, Double> limit= new HashMap<String, Double>();
		int pos;
		for(String s=in.readLine();s!=null;s=in.readLine()){
			l=s.split("\t");
			so= new csvUtils_sortObject(s,l[parentField],Double.parseDouble(l[sortField]));
			if(tops.containsKey(so.getKey())){
				sortList=tops.get(so.getKey());
				if(sortList.size()<number || (so.getValue()<limit.get(so.getKey()))==asc){
					//find position to insert object
					pos=0;
					for(;pos<sortList.size()&&pos<number;pos++){
						if ((so.getValue()<sortList.get(pos).getValue())==asc){
							break;
						}
					}
					sortList.add(pos, so);
					for(;sortList.size()>number;){
						sortList.remove(number);
					}
					limit.put(so.getKey(), sortList.get((sortList.size()<number?sortList.size():number)-1).getValue());
				}
			}else{
				sortList= new ArrayList<csvUtils_sortObject>();
				sortList.add(so);
				tops.put(so.getKey(), sortList);
				limit.put(so.getKey(), so.getValue());
			}
		}
		//print
		for (String key : tops.keySet()) {
			for (csvUtils_sortObject csvUtils_sortObject : tops.get(key)) {
				System.out.println(csvUtils_sortObject.getS());
			}
		}
		in.close();
	}
}
